/*
 * Copyright (c) 2001-2004 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */

package com.caucho.hessian.io;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

/**
 * Input stream for Hessian requests.
 *
 * <p>
 * HessianInput is unbuffered, so any client needs to provide its own buffering.
 *
 * <pre>
 * InputStream is = ...; // from http connection
 * HessianInput in = new HessianInput(is);
 * String value;
 *
 * in.startReply();         // read reply header
 * value = in.readString(); // read string value
 * in.completeReply();      // read reply footer
 * </pre>
 */
public class HessianInput extends AbstractHessianInput {
	private static int END_OF_DATA = -2;

	private static Field _detailMessageField;

	// factory for deserializing objects in the input stream
	protected SerializerFactory _serializerFactory;

	protected ArrayList _refs;

	// the underlying input stream
	private InputStream _is;
	// a peek character
	protected int _peek = -1;

	// the method for a call
	private String _method;

	private Reader _chunkReader;
	private InputStream _chunkInputStream;

	private Throwable _replyFault;

	private StringBuffer _sbuf = new StringBuffer();

	// true if this is the last chunk
	private boolean _isLastChunk;
	// the chunk length
	private int _chunkLength;

	/**
	 * Creates an uninitialized Hessian input stream.
	 */
	public HessianInput() {
	}

	/**
	 * Creates a new Hessian input stream, initialized with an underlying input
	 * stream.
	 *
	 * @param is
	 *            the underlying input stream.
	 */
	public HessianInput(InputStream is) {
		init(is);
	}

	/**
	 * Sets the serializer factory.
	 */
	public void setSerializerFactory(SerializerFactory factory) {
		_serializerFactory = factory;
	}

	/**
	 * Gets the serializer factory.
	 */
	public SerializerFactory getSerializerFactory() {
		return _serializerFactory;
	}

	/**
	 * Initialize the hessian stream with the underlying input stream.
	 */
	public void init(InputStream is) {
		_is = is;
		_method = null;
		_isLastChunk = true;
		_chunkLength = 0;
		_peek = -1;
		_refs = null;
		_replyFault = null;

		if (_serializerFactory == null)
			_serializerFactory = new SerializerFactory();
	}

	/**
	 * Returns the calls method
	 */
	public String getMethod() {
		return _method;
	}

	/**
	 * Returns any reply fault.
	 */
	public Throwable getReplyFault() {
		return _replyFault;
	}

	/**
	 * Starts reading the call
	 *
	 * <pre>
	 * c major minor
	 * </pre>
	 */
	public int readCall() throws IOException {
		int tag = read();

		if (tag != 'c')
			throw error("expected hessian call ('c') at " + codeName(tag));

		int major = read();
		int minor = read();

		return (major << 16) + minor;
	}

	/**
	 * For backward compatibility with HessianSkeleton
	 */
	public void skipOptionalCall() throws IOException {
		int tag = read();

		if (tag == 'c') {
			read();
			read();
		} else
			_peek = tag;
	}

	/**
	 * Starts reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * m b16 b8 method
	 * </pre>
	 */
	public String readMethod() throws IOException {
		int tag = read();

		if (tag != 'm')
			throw error("expected hessian method ('m') at " + codeName(tag));
		int d1 = read();
		int d2 = read();

		_isLastChunk = true;
		_chunkLength = d1 * 256 + d2;
		_sbuf.setLength(0);
		int ch;
		while ((ch = parseChar()) >= 0)
			_sbuf.append((char) ch);

		_method = _sbuf.toString();

		return _method;
	}

	/**
	 * Starts reading the call, including the headers.
	 *
	 * <p>
	 * The call expects the following protocol data
	 *
	 * <pre>
	 * c major minor
	 * m b16 b8 method
	 * </pre>
	 */
	public void startCall() throws IOException {
		readCall();

		while (readHeader() != null) {
			readObject();
		}

		readMethod();
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * z
	 * </pre>
	 */
	public void completeCall() throws IOException {
		int tag = read();

		if (tag == 'z') {
		} else
			throw error("expected end of call ('z') at " + codeName(tag)
					+ ".  Check method arguments and ensure method overloading is enabled if necessary");
	}

	/**
	 * Reads a reply as an object. If the reply has a fault, throws the
	 * exception.
	 */
	public Object readReply(Class expectedClass) throws Throwable {
		int tag = read();

		if (tag != 'r')
			error("expected hessian reply at " + codeName(tag));

		int major = read();
		int minor = read();

		tag = read();
		if (tag == 'f')
			throw prepareFault();
		else {
			_peek = tag;

			Object value = readObject(expectedClass);

			completeValueReply();

			return value;
		}
	}

	/**
	 * Starts reading the reply
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * r
	 * </pre>
	 */
	public void startReply() throws Throwable {
		int tag = read();

		if (tag != 'r')
			error("expected hessian reply at " + codeName(tag));

		int major = read();
		int minor = read();

		startReplyBody();
	}

	public void startReplyBody() throws Throwable {
		int tag = read();

		if (tag == 'f')
			throw prepareFault();
		else
			_peek = tag;
	}

	/**
	 * Prepares the fault.
	 */
	private Throwable prepareFault() throws IOException {
		HashMap fault = readFault();

		Object detail = fault.get("detail");
		String message = (String) fault.get("message");

		if (detail instanceof Throwable) {
			_replyFault = (Throwable) detail;

			if (message != null && _detailMessageField != null) {
				try {
					_detailMessageField.set(_replyFault, message);
				} catch (Throwable e) {
				}
			}

			return _replyFault;
		}

		else {
			String code = (String) fault.get("code");

			_replyFault = new HessianServiceException(message, code, detail);

			return _replyFault;
		}
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * z
	 * </pre>
	 */
	public void completeReply() throws IOException {
		int tag = read();

		if (tag != 'z')
			error("expected end of reply at " + codeName(tag));
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * z
	 * </pre>
	 */
	public void completeValueReply() throws IOException {
		int tag = read();

		if (tag != 'z')
			error("expected end of reply at " + codeName(tag));
	}

	/**
	 * Reads a header, returning null if there are no headers.
	 *
	 * <pre>
	 * H b16 b8 value
	 * </pre>
	 */
	public String readHeader() throws IOException {
		int tag = read();

		if (tag == 'H') {
			_isLastChunk = true;
			_chunkLength = (read() << 8) + read();

			_sbuf.setLength(0);
			int ch;
			while ((ch = parseChar()) >= 0)
				_sbuf.append((char) ch);

			return _sbuf.toString();
		}

		_peek = tag;

		return null;
	}

	/**
	 * Reads a null
	 *
	 * <pre>
	 * N
	 * </pre>
	 */
	public void readNull() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return;

		default:
			throw expect("null", tag);
		}
	}

	/**
	 * Reads a boolean
	 *
	 * <pre>
	 * T
	 * F
	 * </pre>
	 */
	public boolean readBoolean() throws IOException {
		int tag = read();

		switch (tag) {
		case 'T':
			return true;
		case 'F':
			return false;
		case 'I':
			return parseInt() == 0;
		case 'L':
			return parseLong() == 0;
		case 'D':
			return parseDouble() == 0.0;
		case 'N':
			return false;

		default:
			throw expect("boolean", tag);
		}
	}

	/**
	 * Reads a byte
	 *
	 * <pre>
	 * I b32 b24 b16 b8
	 * </pre>
	 */
	/*
	 * public byte readByte() throws IOException { return (byte) readInt(); }
	 */

	/**
	 * Reads a short
	 *
	 * <pre>
	 * I b32 b24 b16 b8
	 * </pre>
	 */
	public short readShort() throws IOException {
		return (short) readInt();
	}

	/**
	 * Reads an integer
	 *
	 * <pre>
	 * I b32 b24 b16 b8
	 * </pre>
	 */
	public int readInt() throws IOException {
		int tag = read();

		switch (tag) {
		case 'T':
			return 1;
		case 'F':
			return 0;
		case 'I':
			return parseInt();
		case 'L':
			return (int) parseLong();
		case 'D':
			return (int) parseDouble();

		default:
			throw expect("int", tag);
		}
	}

	/**
	 * Reads a long
	 *
	 * <pre>
	 * L b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public long readLong() throws IOException {
		int tag = read();

		switch (tag) {
		case 'T':
			return 1;
		case 'F':
			return 0;
		case 'I':
			return parseInt();
		case 'L':
			return parseLong();
		case 'D':
			return (long) parseDouble();

		default:
			throw expect("long", tag);
		}
	}

	/**
	 * Reads a float
	 *
	 * <pre>
	 * D b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public float readFloat() throws IOException {
		return (float) readDouble();
	}

	/**
	 * Reads a double
	 *
	 * <pre>
	 * D b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public double readDouble() throws IOException {
		int tag = read();

		switch (tag) {
		case 'T':
			return 1;
		case 'F':
			return 0;
		case 'I':
			return parseInt();
		case 'L':
			return (double) parseLong();
		case 'D':
			return parseDouble();

		default:
			throw expect("long", tag);
		}
	}

	/**
	 * Reads a date.
	 *
	 * <pre>
	 * T b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	public long readUTCDate() throws IOException {
		int tag = read();

		if (tag != 'd')
			throw error("expected date at " + codeName(tag));

		long b64 = read();
		long b56 = read();
		long b48 = read();
		long b40 = read();
		long b32 = read();
		long b24 = read();
		long b16 = read();
		long b8 = read();

		return ((b64 << 56) + (b56 << 48) + (b48 << 40) + (b40 << 32) + (b32 << 24)
				+ (b24 << 16) + (b16 << 8) + b8);
	}

	/**
	 * Reads a byte from the stream.
	 */
	public int readChar() throws IOException {
		if (_chunkLength > 0) {
			_chunkLength--;
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			int ch = parseUTF8Char();
			return ch;
		} else if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		}

		int tag = read();

		switch (tag) {
		case 'N':
			return -1;

		case 'S':
		case 's':
		case 'X':
		case 'x':
			_isLastChunk = tag == 'S' || tag == 'X';
			_chunkLength = (read() << 8) + read();

			_chunkLength--;
			int value = parseUTF8Char();

			// special code so successive read byte won't
			// be read as a single object.
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			return value;

		default:
			throw new IOException("expected 'S' at " + (char) tag);
		}
	}

	/**
	 * Reads a byte array from the stream.
	 */
	public int readString(char[] buffer, int offset, int length) throws IOException {
		int readLength = 0;

		if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		} else if (_chunkLength == 0) {
			int tag = read();

			switch (tag) {
			case 'N':
				return -1;

			case 'S':
			case 's':
			case 'X':
			case 'x':
				_isLastChunk = tag == 'S' || tag == 'X';
				_chunkLength = (read() << 8) + read();
				break;

			default:
				throw new IOException("expected 'S' at " + (char) tag);
			}
		}

		while (length > 0) {
			if (_chunkLength > 0) {
				buffer[offset++] = (char) parseUTF8Char();
				_chunkLength--;
				length--;
				readLength++;
			} else if (_isLastChunk) {
				if (readLength == 0)
					return -1;
				else {
					_chunkLength = END_OF_DATA;
					return readLength;
				}
			} else {
				int tag = read();

				switch (tag) {
				case 'S':
				case 's':
				case 'X':
				case 'x':
					_isLastChunk = tag == 'S' || tag == 'X';
					_chunkLength = (read() << 8) + read();
					break;

				default:
					throw new IOException("expected 'S' at " + (char) tag);
				}
			}
		}

		if (readLength == 0)
			return -1;
		else if (_chunkLength > 0 || !_isLastChunk)
			return readLength;
		else {
			_chunkLength = END_OF_DATA;
			return readLength;
		}
	}

	/**
	 * Reads a string
	 *
	 * <pre>
	 * S b16 b8 string value
	 * </pre>
	 */
	public String readString() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'I':
			return String.valueOf(parseInt());
		case 'L':
			return String.valueOf(parseLong());
		case 'D':
			return String.valueOf(parseDouble());

		case 'S':
		case 's':
		case 'X':
		case 'x':
			_isLastChunk = tag == 'S' || tag == 'X';
			_chunkLength = (read() << 8) + read();

			_sbuf.setLength(0);
			int ch;

			while ((ch = parseChar()) >= 0)
				_sbuf.append((char) ch);

			return _sbuf.toString();

		default:
			throw expect("string", tag);
		}
	}

	/**
	 * Reads an XML node.
	 *
	 * <pre>
	 * S b16 b8 string value
	 * </pre>
	 */
	public org.w3c.dom.Node readNode() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'S':
		case 's':
		case 'X':
		case 'x':
			_isLastChunk = tag == 'S' || tag == 'X';
			_chunkLength = (read() << 8) + read();

			throw error("Can't handle string in this context");

		default:
			throw expect("string", tag);
		}
	}

	/**
	 * Reads a byte array
	 *
	 * <pre>
	 * B b16 b8 data value
	 * </pre>
	 */
	public byte[] readBytes() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'B':
		case 'b':
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();

			ByteArrayOutputStream bos = new ByteArrayOutputStream();

			int data;
			while ((data = parseByte()) >= 0)
				bos.write(data);

			return bos.toByteArray();

		default:
			throw expect("bytes", tag);
		}
	}

	/**
	 * Reads a byte from the stream.
	 */
	public int readByte() throws IOException {
		if (_chunkLength > 0) {
			_chunkLength--;
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			return read();
		} else if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		}

		int tag = read();

		switch (tag) {
		case 'N':
			return -1;

		case 'B':
		case 'b':
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();

			int value = parseByte();

			// special code so successive read byte won't
			// be read as a single object.
			if (_chunkLength == 0 && _isLastChunk)
				_chunkLength = END_OF_DATA;

			return value;

		default:
			throw new IOException("expected 'B' at " + (char) tag);
		}
	}

	/**
	 * Reads a byte array from the stream.
	 */
	public int readBytes(byte[] buffer, int offset, int length) throws IOException {
		int readLength = 0;

		if (_chunkLength == END_OF_DATA) {
			_chunkLength = 0;
			return -1;
		} else if (_chunkLength == 0) {
			int tag = read();

			switch (tag) {
			case 'N':
				return -1;

			case 'B':
			case 'b':
				_isLastChunk = tag == 'B';
				_chunkLength = (read() << 8) + read();
				break;

			default:
				throw new IOException("expected 'B' at " + (char) tag);
			}
		}

		while (length > 0) {
			if (_chunkLength > 0) {
				buffer[offset++] = (byte) read();
				_chunkLength--;
				length--;
				readLength++;
			} else if (_isLastChunk) {
				if (readLength == 0)
					return -1;
				else {
					_chunkLength = END_OF_DATA;
					return readLength;
				}
			} else {
				int tag = read();

				switch (tag) {
				case 'B':
				case 'b':
					_isLastChunk = tag == 'B';
					_chunkLength = (read() << 8) + read();
					break;

				default:
					throw new IOException("expected 'B' at " + (char) tag);
				}
			}
		}

		if (readLength == 0)
			return -1;
		else if (_chunkLength > 0 || !_isLastChunk)
			return readLength;
		else {
			_chunkLength = END_OF_DATA;
			return readLength;
		}
	}

	/**
	 * Reads a fault.
	 */
	private HashMap readFault() throws IOException {
		HashMap map = new HashMap();

		int code = read();
		for (; code > 0 && code != 'z'; code = read()) {
			_peek = code;

			Object key = readObject();
			Object value = readObject();

			if (key != null && value != null)
				map.put(key, value);
		}

		if (code != 'z')
			throw expect("fault", code);

		return map;
	}

	/**
	 * Reads an object from the input stream with an expected type.
	 */
	public Object readObject(Class cl) throws IOException {
		if (cl == null || cl == Object.class)
			return readObject();

		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'M': {
			String type = readType();

			// hessian/3386
			if ("".equals(type)) {
				Deserializer reader;
				reader = _serializerFactory.getDeserializer(cl);

				return reader.readMap(this);
			} else {
				Deserializer reader;
				reader = _serializerFactory.getObjectDeserializer(type);

				return reader.readMap(this);
			}
		}

		case 'V': {
			String type = readType();
			int length = readLength();

			Deserializer reader;
			reader = _serializerFactory.getObjectDeserializer(type);

			if (cl != reader.getType() && cl.isAssignableFrom(reader.getType()))
				return reader.readList(this, length);

			reader = _serializerFactory.getDeserializer(cl);

			Object v = reader.readList(this, length);

			return v;
		}

		case 'R': {
			int ref = parseInt();

			return _refs.get(ref);
		}

		case 'r': {
			String type = readType();
			String url = readString();

			return resolveRemote(type, url);
		}
		}

		_peek = tag;

		// hessian/332i vs hessian/3406
		// return readObject();

		Object value = _serializerFactory.getDeserializer(cl).readObject(this);

		return value;
	}

	/**
	 * Reads an arbitrary object from the input stream when the type is unknown.
	 */
	public Object readObject() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'T':
			return Boolean.valueOf(true);

		case 'F':
			return Boolean.valueOf(false);

		case 'I':
			return Integer.valueOf(parseInt());

		case 'L':
			return Long.valueOf(parseLong());

		case 'D':
			return Double.valueOf(parseDouble());

		case 'd':
			return new Date(parseLong());

		case 'x':
		case 'X': {
			_isLastChunk = tag == 'X';
			_chunkLength = (read() << 8) + read();

			return parseXML();
		}

		case 's':
		case 'S': {
			_isLastChunk = tag == 'S';
			_chunkLength = (read() << 8) + read();

			int data;
			_sbuf.setLength(0);

			while ((data = parseChar()) >= 0)
				_sbuf.append((char) data);

			return _sbuf.toString();
		}

		case 'b':
		case 'B': {
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();

			int data;
			ByteArrayOutputStream bos = new ByteArrayOutputStream();

			while ((data = parseByte()) >= 0)
				bos.write(data);

			return bos.toByteArray();
		}

		case 'V': {
			String type = readType();
			int length = readLength();

			return _serializerFactory.readList(this, length, type);
		}

		case 'M': {
			String type = readType();

			return _serializerFactory.readMap(this, type);
		}

		case 'R': {
			int ref = parseInt();

			return _refs.get(ref);
		}

		case 'r': {
			String type = readType();
			String url = readString();

			return resolveRemote(type, url);
		}

		default:
			throw error("unknown code for readObject at " + codeName(tag));
		}
	}

	/**
	 * Reads a remote object.
	 */
	public Object readRemote() throws IOException {
		String type = readType();
		String url = readString();

		return resolveRemote(type, url);
	}

	/**
	 * Reads a reference.
	 */
	public Object readRef() throws IOException {
		return _refs.get(parseInt());
	}

	/**
	 * Reads the start of a list.
	 */
	public int readListStart() throws IOException {
		return read();
	}

	/**
	 * Reads the start of a list.
	 */
	public int readMapStart() throws IOException {
		return read();
	}

	/**
	 * Returns true if this is the end of a list or a map.
	 */
	public boolean isEnd() throws IOException {
		int code = read();

		_peek = code;

		return (code < 0 || code == 'z');
	}

	/**
	 * Reads the end byte.
	 */
	public void readEnd() throws IOException {
		int code = read();

		if (code != 'z')
			throw error("unknown code at " + codeName(code));
	}

	/**
	 * Reads the end byte.
	 */
	public void readMapEnd() throws IOException {
		int code = read();

		if (code != 'z')
			throw error("expected end of map ('z') at " + codeName(code));
	}

	/**
	 * Reads the end byte.
	 */
	public void readListEnd() throws IOException {
		int code = read();

		if (code != 'z')
			throw error("expected end of list ('z') at " + codeName(code));
	}

	/**
	 * Adds a list/map reference.
	 */
	public int addRef(Object ref) {
		if (_refs == null)
			_refs = new ArrayList();

		_refs.add(ref);

		return _refs.size() - 1;
	}

	/**
	 * Adds a list/map reference.
	 */
	public void setRef(int i, Object ref) {
		_refs.set(i, ref);
	}

	/**
	 * Resets the references for streaming.
	 */
	public void resetReferences() {
		if (_refs != null)
			_refs.clear();
	}

	/**
	 * Resolves a remote object.
	 */
	public Object resolveRemote(String type, String url) throws IOException {
		HessianRemoteResolver resolver = getRemoteResolver();

		if (resolver != null)
			return resolver.lookup(type, url);
		else
			return new HessianRemote(type, url);
	}

	/**
	 * Parses a type from the stream.
	 *
	 * <pre>
	 * t b16 b8
	 * </pre>
	 */
	public String readType() throws IOException {
		int code = read();

		if (code != 't') {
			_peek = code;
			return "";
		}

		_isLastChunk = true;
		_chunkLength = (read() << 8) + read();

		_sbuf.setLength(0);
		int ch;
		while ((ch = parseChar()) >= 0)
			_sbuf.append((char) ch);

		return _sbuf.toString();
	}

	/**
	 * Parses the length for an array
	 *
	 * <pre>
	 * l b32 b24 b16 b8
	 * </pre>
	 */
	public int readLength() throws IOException {
		int code = read();

		if (code != 'l') {
			_peek = code;
			return -1;
		}

		return parseInt();
	}

	/**
	 * Parses a 32-bit integer value from the stream.
	 *
	 * <pre>
	 * b32 b24 b16 b8
	 * </pre>
	 */
	private int parseInt() throws IOException {
		int b32 = read();
		int b24 = read();
		int b16 = read();
		int b8 = read();

		return (b32 << 24) + (b24 << 16) + (b16 << 8) + b8;
	}

	/**
	 * Parses a 64-bit long value from the stream.
	 *
	 * <pre>
	 * b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	private long parseLong() throws IOException {
		long b64 = read();
		long b56 = read();
		long b48 = read();
		long b40 = read();
		long b32 = read();
		long b24 = read();
		long b16 = read();
		long b8 = read();

		return ((b64 << 56) + (b56 << 48) + (b48 << 40) + (b40 << 32) + (b32 << 24)
				+ (b24 << 16) + (b16 << 8) + b8);
	}

	/**
	 * Parses a 64-bit double value from the stream.
	 *
	 * <pre>
	 * b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	private double parseDouble() throws IOException {
		long b64 = read();
		long b56 = read();
		long b48 = read();
		long b40 = read();
		long b32 = read();
		long b24 = read();
		long b16 = read();
		long b8 = read();

		long bits = ((b64 << 56) + (b56 << 48) + (b48 << 40) + (b40 << 32) + (b32 << 24)
				+ (b24 << 16) + (b16 << 8) + b8);

		return Double.longBitsToDouble(bits);
	}

	org.w3c.dom.Node parseXML() throws IOException {
		throw new UnsupportedOperationException();
	}

	/**
	 * Reads a character from the underlying stream.
	 */
	private int parseChar() throws IOException {
		while (_chunkLength <= 0) {
			if (_isLastChunk)
				return -1;

			int code = read();

			switch (code) {
			case 's':
			case 'x':
				_isLastChunk = false;

				_chunkLength = (read() << 8) + read();
				break;

			case 'S':
			case 'X':
				_isLastChunk = true;

				_chunkLength = (read() << 8) + read();
				break;

			default:
				throw expect("string", code);
			}

		}

		_chunkLength--;

		return parseUTF8Char();
	}

	/**
	 * Parses a single UTF8 character.
	 */
	private int parseUTF8Char() throws IOException {
		int ch = read();

		if (ch < 0x80)
			return ch;
		else if ((ch & 0xe0) == 0xc0) {
			int ch1 = read();
			int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);

			return v;
		} else if ((ch & 0xf0) == 0xe0) {
			int ch1 = read();
			int ch2 = read();
			int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);

			return v;
		} else
			throw error("bad utf-8 encoding at " + codeName(ch));
	}

	/**
	 * Reads a byte from the underlying stream.
	 */
	private int parseByte() throws IOException {
		while (_chunkLength <= 0) {
			if (_isLastChunk) {
				return -1;
			}

			int code = read();

			switch (code) {
			case 'b':
				_isLastChunk = false;

				_chunkLength = (read() << 8) + read();
				break;

			case 'B':
				_isLastChunk = true;

				_chunkLength = (read() << 8) + read();
				break;

			default:
				throw expect("byte[]", code);
			}
		}

		_chunkLength--;

		return read();
	}

	/**
	 * Reads bytes based on an input stream.
	 */
	public InputStream readInputStream() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'B':
		case 'b':
			_isLastChunk = tag == 'B';
			_chunkLength = (read() << 8) + read();
			break;

		default:
			throw expect("inputStream", tag);
		}

		return new InputStream() {
			boolean _isClosed = false;

			public int read() throws IOException {
				if (_isClosed || _is == null)
					return -1;

				int ch = parseByte();
				if (ch < 0)
					_isClosed = true;

				return ch;
			}

			public int read(byte[] buffer, int offset, int length) throws IOException {
				if (_isClosed || _is == null)
					return -1;

				int len = HessianInput.this.read(buffer, offset, length);
				if (len < 0)
					_isClosed = true;

				return len;
			}

			public void close() throws IOException {
				while (read() >= 0) {
				}

				_isClosed = true;
			}
		};
	}

	/**
	 * Reads bytes from the underlying stream.
	 */
	int read(byte[] buffer, int offset, int length) throws IOException {
		int readLength = 0;

		while (length > 0) {
			while (_chunkLength <= 0) {
				if (_isLastChunk)
					return readLength == 0 ? -1 : readLength;

				int code = read();

				switch (code) {
				case 'b':
					_isLastChunk = false;

					_chunkLength = (read() << 8) + read();
					break;

				case 'B':
					_isLastChunk = true;

					_chunkLength = (read() << 8) + read();
					break;

				default:
					throw expect("byte[]", code);
				}
			}

			int sublen = _chunkLength;
			if (length < sublen)
				sublen = length;

			sublen = _is.read(buffer, offset, sublen);
			offset += sublen;
			readLength += sublen;
			length -= sublen;
			_chunkLength -= sublen;
		}

		return readLength;
	}

	final int read() throws IOException {
		if (_peek >= 0) {
			int value = _peek;
			_peek = -1;
			return value;
		}

		int ch = _is.read();

		return ch;
	}

	public void close() {
		_is = null;
	}

	public Reader getReader() {
		return null;
	}

	protected IOException expect(String expect, int ch) {
		return error("expected " + expect + " at " + codeName(ch));
	}

	protected String codeName(int ch) {
		if (ch < 0)
			return "end of file";
		else
			return "0x" + Integer.toHexString(ch & 0xff) + " (" + (char) +ch + ")";
	}

	protected IOException error(String message) {
		if (_method != null)
			return new HessianProtocolException(_method + ": " + message);
		else
			return new HessianProtocolException(message);
	}

	static {
		try {
			_detailMessageField = Throwable.class.getDeclaredField("detailMessage");
			_detailMessageField.setAccessible(true);
		} catch (Throwable e) {
		}
	}
}
